<?php

    #-------------------------------------------------------------#
	# This file is part of the Tekuna MVC Microframework for PHP. #
	# (c) 2009 Niels Jäckel <niels.jaeckel@tekuna.org>            #
	# Please visit http://tekuna.org/ for more information.       #
	#                                                             #
	# This software is licensed under the MIT License. Please     #
	# view the LICENSE file for the full text of this license.    #
    #-------------------------------------------------------------#


	Tekuna :: load('org.tekuna.core.application.TekunaModuleProcessor');
	Tekuna :: load('org.tekuna.core.application.TekunaApplicationContext');


	/**
	 * The default module processor implementation.
	 */

	class DefaultModuleProcessor implements TekunaModuleProcessor {

		protected $objContext = NULL;


		/**
		 * DefaultModuleProcessor's constructor.
		 *
		 * @param TekunaApplicationContext $objContext the current application context.
		 */

		public function __construct(TekunaApplicationContext $objContext) {

			$this -> objContext = $objContext;
		}


		/**
		 * Processes the given module and returns an associative array with
		 * the outputs of all slots.
		 *
		 * @param ApplicationModule $objModule
		 */

		public function processModule(ApplicationModule $objModule) {

			// initialize the slots array
			$arrSlotContents = array();

			// set HTTP status if given
			if ($objModule -> hasStatus()) {

				$this -> objContext -> getResponse() -> setStatus($objModule -> getStatus());
			}

			// get the component's list
			$arrComp = $objModule -> getAllComponents();

			// execute all components
			foreach ($arrComp as $objComponent) {

				// set HTTP status if given
				if ($objComponent -> hasStatus()) {

					$this -> objContext -> getResponse() -> setStatus($objComponent -> getStatus());
				}

				$sComponentOutput = '';
				try {

					// handle fragment if given
					if ($objComponent -> hasFragment()) {

						$sComponentOutput = $this -> processFragment($objComponent);
					}

					// handle controller if given
					if ($objComponent -> hasController()) {

						$sComponentOutput = $this -> processController($objComponent);
					}

					// handle routes if given
					if ($objComponent -> hasRoutes()) {

						$sComponentOutput = $this -> processRoutes($objComponent);
					}
				}
				catch (Exception $e) {

					// for security reasons catch all exceptions here.
					// show only the type and the message
					$sComponentOutput = '<div class="TekunaException">';
					$sComponentOutput .= '<h1>';
					$sComponentOutput .= get_class($e);
					$sComponentOutput .= '</h1>';
					$sComponentOutput .= '<p>';
					$sComponentOutput .= nl2br($e -> getMessage());
					$sComponentOutput .= '</p>';
					$sComponentOutput .= '<p>Please have a look into the log/exceptions.log file to get more information.</p>';
					$sComponentOutput .= '</div>';

					// but log the whole thing
					log_exception($e);
				}

				// determine component target slot
				$sSlot = $objComponent -> getSlot();

				// store output with the slot key
				$arrSlotContents[$sSlot] = $sComponentOutput;
			}

			// return the slot contents array
			return $arrSlotContents;
		}



		protected function processFragment(ApplicationComponent $objComponent, $sFragment = NULL) {

			// get the configured fragment if not given
			if ($sFragment === NULL) {

				$sFragment = $objComponent -> getFragment();
			}

			// build the full path out of it
			$sFragmentPath = TekunaApplication :: BASEDIR_FRAGMENTS .
			                 $this -> objContext -> getApplicationLanguage() .
			                 DIRECTORY_SEPARATOR .
			                 $sFragment;

			$sFullFragmentPath = APPLICATION_ROOT . $sFragmentPath;

			// check existence and readability
			if (! file_exists($sFullFragmentPath) || ! is_readable($sFullFragmentPath)) {

				$sSlot = $objComponent->getSlot();
				throw new TekunaApplicationException("The fragment for slot '$sSlot' could not be loaded. The fragment '$sFragmentPath' does not exist or is not readable.");
			}

			// and now just get the contents
			try {

				return file_get_contents($sFullFragmentPath);
			}
			catch (Exception $e) {

				$sSlot = $objComponent->getSlot();
				$sCauseType = get_class($e);
				throw new TekunaApplicationException("The fragment for slot '$sSlot' could not be loaded.\n\nCause: $sCauseType", $e);
			}
		}


		protected function processController(ApplicationComponent $objComponent, $sController = NULL) {

			// get the configured fragment if the controller is not given
			if ($sController === NULL) {

				$sController = $objComponent -> getController();
			}

			try {

				// get the instance
				$objController = TekunaApplication :: getControllerInstance($sController, $this -> objContext);

				// process controller and return output
				return $objController -> getOutput();
			}
			catch (TekunaApplicationException $e) {

				$sSlot = $objComponent->getSlot();
				throw new TekunaApplicationException("The controller for slot '$sSlot' could not be executed. ". $e -> getMessage(), $e);
			}
			catch (ClassNotFoundException $e) {

				$sSlot = $objComponent -> getSlot();
				throw new TekunaApplicationException("The controller '$sController' for slot '$sSlot' could not be executed. ". $e -> getMessage(), $e);
			}
			catch (Exception $e) {

				$sSlot = $objComponent -> getSlot();
				$sCauseType = get_class($e);
				throw new TekunaApplicationException("The controller for slot '$sSlot' could not be executed.\n\nCause: $sCauseType", $e);
			}
		}


		protected function processRoutes(ApplicationComponent $objComponent) {

			// get the last 'not routed' part
			$sComponentURL = $this -> objContext -> getURLInformation() -> getComponentURL();

			// iterate all defined routes
			foreach ($objComponent -> getAllRoutes() as $objRoute) {

				// extract the defined pattern
				$sRoutePattern = $objRoute -> getPattern();

				// build a regular expression from this pattern
				// prepend a backslash for special regex characters
				$sRouteRegEx = preg_replace('~[\~\\\\\.\+\[\]\^\$\(\)\{\}\=\!\<\>\|ß:\?]~', '\\\${0}', $sRoutePattern);
				$sRouteRegEx = str_replace('*', '([^/]*?)', $sRouteRegEx);
				$sRouteRegEx = str_replace(/*'**'*/'([^/]*?)([^/]*?)', '(.*?)', $sRouteRegEx);
				$sRouteRegEx = "~^$sRouteRegEx\$~";

				// try to match the url against the regular expression
				if (preg_match($sRouteRegEx, $sComponentURL, $arrMatches)) {

					// delete not necessary full match
					unset($arrMatches[0]);

					// store matches
					$this -> objContext -> getURLInformation() -> setMatchedURLParts($arrMatches);

					// set HTTP status if given
					if ($objRoute -> hasStatus()) {

						$this -> objContext -> getResponse() -> setStatus($objRoute -> getStatus());
					}

					// handle the fragment, if the fragment is given
					if ($objRoute -> hasFragment()) {

						// process the surrounding component and the fragment explicitly given
						return $this -> processFragment($objComponent, $objRoute -> getFragment());
					}

					// handle the controller, if given
					if ($objRoute -> hasController()) {

						// process the surrounding component and the controller explicitly given
						return $this -> processController($objComponent, $objRoute -> getController());
					}
				}
			}

			// return empty string if nothing matched.
			return '';
		}
	}
